import { onLoad, onShow, onUnload } from '@dcloudio/uni-app'
import type { Base } from './types/base'
import type { Worker } from './types/worker'
import type { BaseController } from '../BaseAppliance/definition'
import { DebouncedWarning, getPageQuery } from '@/utils/mpHelper'
import { ApplianceBinder } from './binder'
import type {
  ApplianceCommand,
  ApplianceDetail,
  ApplianceDetailOption,
  ApplianceSetting,
  ApplianceStatus,
  ProgramAvailable,
} from '@/api/appliance'
import type { WS } from '@/utils/socket/types'
import type { Binder } from './types/binder'
import type { WebsocketHandlerImp } from '@/utils/socket/WebSocketHandlerImp'
import { useRemote } from './index'
import type { WatchStopHandle } from 'vue'

/**
 * 数据处理和控制
 */
export class ApplianceWorker<Store extends Base.FullStore> implements Worker.ApplianceWorker<Store> {
  store!: Store

  // Websocket
  websocket!: WebsocketHandlerImp

  // 数据绑定
  settingBinder!: ApplianceBinder<Store, ApplianceSetting>
  statusBinder!: ApplianceBinder<Store, ApplianceStatus>
  detailOptionBinder!: ApplianceBinder<Store, ApplianceDetailOption, ApplianceDetail | null>
  availableBinder!: ApplianceBinder<Store, ProgramAvailable.Option, ProgramAvailable.Scope | null>
  eventBinder!: ApplianceBinder<Store, WS.MessageItem>
  commandBinder!: ApplianceBinder<Store, ApplianceCommand>
  detailBindings: Map<Binder.PropertyName, Worker.DetailBindingGetter<any>> = new Map()
  stopDetailBinder: WatchStopHandle | null = null

  // 生命周期
  onLoadCallbacks: Worker.OnLoadCallback[] = []
  onLoadedCallbacks: Worker.OnLoadCallback[] = []
  onShowCallbacks: Worker.OnShowCallback[] = []
  onResetCallbacks: Worker.OnResetCallback[] = []

  private get isVirtualAppliance() {
    return (this.store as BaseController.Store).isVirtualAppliance || false
  }

  constructor(store: Store) {
    this.store = store
  }
  init() {
    // 生命周期
    this.initLifecycles()

    // 数据绑定
    this.initBindings()
  }

  private reset() {
    this.resetBindings()
    this.resetLifecycles()
    this.store._datasource.loaded = false
    this.store._datasource.loadSuccess = false
  }

  /**
   * 生命周期
   */
  private initLifecycles() {
    onLoad(async (query) => {
      query?.vib && (this.store._datasource.vib = query.vib)
      query?.haId && (this.store._datasource.haId = query.haId)
      query?.type && (this.store._datasource.type = query.type)
      query?.brand && (this.store._datasource.brand = query.brand)

      // 触发onLoad回调
      this.triggerOnLoad(query)
    })
    onUnload(() => this.reset())
    onShow(() => this.triggerOnShow())
  }
  private resetLifecycles() {
    this.onResetCallbacks.forEach((cb) => cb())
    this.onLoadCallbacks = []
    this.onShowCallbacks = []
    this.onResetCallbacks = []
  }
  async triggerOnLoad(query?: Record<string, any>) {
    query = query || getPageQuery()
    try {
      // 先加载detail，根据detail的connected状态判断
      const store = this.store as BaseController.Store
      // 真实家电 调用detail接口查询
      if (!store.isVirtualAppliance) {
        const remote = useRemote(store)
        await remote.loadApplianceDetail()
      }
      await this.loadCallbacks(this.onLoadedCallbacks, query)

      // 在线时 调用onLoad回调
      if (store._datasource.detail?.connected) {
        await this.loadCallbacks(this.onLoadCallbacks, query)
      }
      // 真实家电离线时 加载缓存
      else if (!store.isVirtualAppliance && !['TT', 'HVN'].includes(store._datasource.type)) {
        const remote = useRemote(this.store)
        await remote.loadFeatures()
      }

      this.store._datasource.loadSuccess = true
    } catch (e) {
      this.store._datasource.loadSuccess = false
      console.error(e)
    } finally {
      this.store._datasource.loaded = true
    }
  }
  triggerOnShow() {
    return this.loadCallbacks(this.onShowCallbacks)
  }
  onLoad(callback: Worker.OnLoadCallback) {
    this.onLoadCallbacks.push(callback)
  }
  onLoaded(callback: Worker.OnLoadCallback) {
    this.onLoadedCallbacks.push(callback)
  }
  onShow(callback: Worker.OnShowCallback) {
    this.onShowCallbacks.push(callback)
  }
  onReset(callback: Worker.OnResetCallback) {
    this.onResetCallbacks.push(callback)
  }
  private async loadCallbacks<T>(loaders: ((...args: T[]) => Promise<any>)[], ...args: T[]) {
    const tasks = loaders.map((loader) => {
      return loader(...args).catch((e) => {
        if (e === DebouncedWarning) {
          console.error(e)
          return Promise.resolve()
        }
        return Promise.reject(e)
      })
    })
    await Promise.all(tasks)
  }

  /**
   * 数据绑定
   */
  private initBindings() {
    let websocket: WebsocketHandlerImp | undefined = void 0
    // 非虚拟家电 获取websocket
    if (!this.isVirtualAppliance) {
      const remote = useRemote(this.store)
      websocket = remote.websocket
    }

    this.settingBinder = new ApplianceBinder({
      store: this.store,
      websocket,
      trigger: () => this.store._datasource.settings,
    })
    this.statusBinder = new ApplianceBinder({
      store: this.store,
      websocket,
      trigger: () => this.store._datasource.status,
    })
    this.detailOptionBinder = new ApplianceBinder({
      store: this.store,
      websocket,
      trigger: () => this.store._datasource.detail,
      listGetter: (detail) => detail?.option || [],
    })
    this.availableBinder = new ApplianceBinder({
      store: this.store,
      websocket,
      trigger: () => (this.store as BaseController.Store).availableParameter,
      listGetter: (available) => available?.options || [],
    })
    this.eventBinder = new ApplianceBinder({
      store: this.store,
      websocket,
      trigger: () => this.store._datasource.events,
    })
    this.commandBinder = new ApplianceBinder({
      store: this.store,
      websocket,
      trigger: () => this.store._datasource.commands,
    })

    this.stopDetailBinder = watch(
      () => this.store._datasource.detail,
      (detail) => {
        if (!detail) return

        this.handleDetailPatch(detail)
      },
      { immediate: true }
    )

    this.onLoad(async () => {
      this.startBindings()
    })
  }
  private resetBindings() {
    this.stopBindings()
    this.stopDetailBinder?.()
  }
  private startBindings() {
    this.settingBinder.startBinding()
    this.statusBinder.startBinding()
    this.detailOptionBinder.startBinding()
    this.availableBinder.startBinding()
    this.eventBinder.startBinding()
    this.commandBinder.startBinding()
  }
  private stopBindings() {
    this.settingBinder.stopBinding()
    this.statusBinder.stopBinding()
    this.detailOptionBinder.stopBinding()
    this.availableBinder.stopBinding()
    this.eventBinder.stopBinding()
    this.commandBinder.stopBinding()
  }
  private getBinder(from: Worker.BindingFrom) {
    switch (from) {
      case 'setting':
        return this.settingBinder
      case 'status':
        return this.statusBinder
      case 'detailOption':
        return this.detailOptionBinder
      case 'available':
        return this.availableBinder
      case 'event':
        return this.eventBinder
      case 'command':
        return this.commandBinder
    }
  }
  useGetValueBinding(prop: keyof Store['$state'], options: Worker.GetValueBindingOptions<Store>) {
    const binder = this.getBinder(options.from)
    const useMessage = this.isVirtualAppliance ? 'ignore' : options.useMessage || 'use'

    binder.addBinding(
      options.key,
      (state, reference, invoker) => {
        // 跳过赋值
        if (typeof options.shouldPatch === 'function' && !options.shouldPatch(reference as any)) return

        if (options.patcher) return options.patcher(state, reference as Binder.BindingReference<any>, invoker)
        // 直接赋值
        const patchValue = reference.value as Binder.PatchState<Store>[typeof prop]
        ;(state as Binder.PatchState<Store>)[prop] = patchValue
      },
      useMessage
    )
  }
  useEqualValueBinding(prop: keyof Store['$state'], options: Worker.EqualValueBindingOptions<Store>) {
    const binder = this.getBinder(options.from)
    const useMessage = this.isVirtualAppliance ? 'ignore' : options.useMessage || 'use'

    binder.addBinding(
      options.key,
      (state, reference, invoker) => {
        // 跳过赋值
        if (typeof options.shouldPatch === 'function' && !options.shouldPatch(reference as any)) return

        if (options.patcher) return options.patcher(state, reference as Binder.BindingReference<any>, invoker)
        // 相等判断表达式结果
        const isEqual = reference.value === options.value
        ;(state as Binder.PatchState<Store>)[prop] = isEqual
      },
      useMessage
    )
  }
  useHasKeyBinding(prop: keyof Store['$state'], options: Worker.HasKeyBindingOptions<Store>) {
    const binder = this.getBinder(options.from)

    binder.addConditionBinding([options.key], (state, references, invoker) => {
      // 跳过赋值
      if (typeof options.shouldPatch === 'function' && !options.shouldPatch(references as any)) return

      if (options.patcher) return options.patcher(state, references as any, invoker)

      // 赋值是否存在
      const isExist = references.some((ref) => ref?.item.key === options.key)
      ;(state as Binder.PatchState<Store>)[prop] = isExist
    })
  }
  useHasKeysBinding(prop: keyof Store['$state'], options: Worker.HasKeysBindingOptions<Store>) {
    const binder = this.getBinder(options.from)

    binder.addConditionBinding(options.keys, (state, references, invoker) => {
      // 跳过赋值
      if (typeof options.shouldPatch === 'function' && !options.shouldPatch(references as any)) return

      if (options.patcher) return options.patcher(state, references as any, invoker)

      // 查找匹配
      let matched = false
      // 全匹配类型
      if (options.match === 'all') {
        matched = (references as any[]).every((ref) => ref !== null)
      }
      // 部分匹配类型 (默认类型)
      else {
        matched = references.some((ref) => ref !== null)
      }

      // 赋值是否存在
      ;(state as Binder.PatchState<Store>)[prop] = matched
    })
  }
  useDetailBinding(
    prop: keyof Store['$state'],
    value: Worker.DetailBindingGetter<Store['$state'][typeof prop]> | Worker.DetailBindingOptions<Store>
  ) {
    const getter = typeof value === 'function' ? value : (detail: ApplianceDetail) => detail[value.field]
    this.detailBindings.set(prop as string, getter)
  }

  private handleDetailPatch(detail: ApplianceDetail) {
    const patchState = Array.from(this.detailBindings).reduce<Binder.PatchState<Store>>(
      (patchState, [stateProp, valueGetter]) => {
        try {
          const value = valueGetter(detail)
          if (value !== void 0) {
            patchState[stateProp as keyof Binder.PatchState<Store>] = value
          }
        } catch (e) {
          console.error(`Detail Getter for state property "${stateProp}" has some error(s)`)
        }
        return patchState
      },
      {}
    )
    this.store.$patch(patchState)
  }
}
